<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <style>
      #wave {
        width: 400px;
        height: 200px;
      }
    </style>
  </head>

  <body>
    <button id="play">play</button>
    <canvas id="wave"></canvas>
    <script>
      wave.height = 300 * window.devicePixelRatio;
      wave.width = 400 * window.devicePixelRatio;
      var waveCtx = wave.getContext("2d");

      var ac = new AudioContext();
      var analyser = ac.createAnalyser();
      var timer = null;
      var TONES = new Array(88)
        .fill(0)
        .map((item, key) => 27.5 * Math.pow(Math.pow(2, 1 / 12), key));

      function renderWave() {
        var analyserArr = new Uint8Array(analyser.frequencyBinCount);
        analyser.getByteFrequencyData(analyserArr);
        console.log(analyserArr);
        analyserArr.map((item) => {});

        waveCtx.clearRect(0, 0, wave.width, wave.height);
        // waveCtx.scale(1, -1);
        waveCtx.beginPath();
        waveCtx.moveTo(0, wave.height);

        for (var i = 0; i < wave.width; i++) {
          waveCtx.lineTo(
            i,
            wave.height -
              (wave.height / 255) *
                analyserArr[~~((i * analyserArr.length) / wave.width)]
          );
        }
        waveCtx.lineTo(wave.width, wave.height);
        waveCtx.strokeStyle = "#f00";
        waveCtx.stroke();
        waveCtx.fill();

        requestAnimationFrame(renderWave);
      }

      const aa = () => {
        requestAnimationFrame(aa);
      };
      aa();

      renderWave();

      var getToneFreq = (tone) => {
        var _getTone = /(^[1-7])(#)?([\'\,])*/.exec(tone);

        if (!_getTone) {
          return 0;
        }

        var _toneStr = _getTone[0];
        var _tone = +_getTone[1];
        var _isSharp = !!_getTone[2];
        var _subCount = (_toneStr.match(/,/g) || []).length;
        var _upCount = (_toneStr.match(/'/g) || []).length;

        if (_tone <= 3) {
          _tone = _tone * 2 - 1 + (_isSharp ? 1 : 0);
        } else {
          _tone = _tone + (_tone - 4) + (_isSharp ? 1 : 0);
        }

        return TONES[_tone - 1 + 39 + (_upCount - _subCount) * 12];
      };

      var getToneTime = (time) => {
        var _count = (time.match(/\//g) || []).length;
        var _lineCount = (time.match(/-/g) || []).length;
        var _hasDotted = time.indexOf(".") > -1;
        var TIME_LEN = 0.5;
        if (!_count) {
          return (
            TIME_LEN + (_hasDotted ? TIME_LEN * 0.5 : 0) + TIME_LEN * _lineCount
          );
        }
        return (
          TIME_LEN / Math.pow(2, _count) +
          (_hasDotted ? (TIME_LEN / Math.pow(2, _count)) * 0.5 : 0) +
          _lineCount * TIME_LEN
        );
      };

      var getSongTone = (song) => {
        return song
          .replace(/\|/g, "")
          .replace(/\n/g, " ")
          .split(" ")
          .filter((item) => item)
          .map((item) => ({
            tone: getToneFreq(item),
            time: getToneTime(item),
          }));
      };

      play.onclick = (e) => {
        var song = `
      6/ 6 5/ 6/ 6 5/ | 6/ 6 5/ 6 1' | 6/ 6 5/ 6/ 6 5/ | 6 1' 2' 3' |
      2/ 3/ 6,// 5,// 6,// 5,// 2/ 3/ 6,// 5,// 6,// 5,// | 2/ 3/ 6,// 5,// 6,// 5,// 1/ 7,/ 6,/ 5,/ | 2/ 3/ 6,// 5,// 6,// 5,// 2/ 3/ 6,// 5,// 6,// 5,// | 2/ 3/ 5/ 1'/ 7// 1'// 7// 6// 5/ 3/ |
      2/ 3/ 6,// 5,// 6,// 5,// 2/ 3/ 6,// 5,// 6,// 5,// | 2/ 3/ 6,// 5,// 6,// 5,// 1/ 7,/ 6,/ 5,/ | 1/ 6,// 1// 2/ 1// 2// 3/ 2// 3// 5// 1'// 3// 5// | 1'/ 7/ 6/ 5/ 6 6/ 1'/ |
      2/ 3/ 6,// 5,// 6,// 5,// 2/ 3/ 6,// 5,// 6,// 5,// | 2/ 3/ 6,// 5,// 6,// 5,// 1/ 7,/ 6/ 5/ | 2/ 3/ 6,// 5,// 6,// 5,// 2/ 3/ 6,// 5,// 6,// 5,// | 2'/ 3'/ 5'/ 1''/ 7'// 1''// 7'// 6'// 5'/ 3'/ |
      2/ 3/ 6,// 5,// 6,// 5,// 2/ 3/ 6,// 5,// 6,// 5,// | 2/ 3/ 6,// 5,// 6,// 5,// 1/ 7,/ 6/ 5/ | 3'// 2'// 3'// 5'// 6'// 5'// 3'// 2'// 6/ 1'/ 3'/ 5'/ | 6'/ 6' 5'/ 6'- |
      6 6/. 5// 6/ 1'/ 2'/ 3'/ | 6 6/. 5// 6/ 5/ 3/ 5/ | 6 6/. 5// 6/ 1'/ 2'/ 3'/ | 3' 2' 1' 6 |
      6 6/. 5// 6/ 1'/ 2'/ 3'/ | 6 6/. 5// 6/ 5/ 5/ 3/ | 6 6/. 5// 5/ 6/ 1'/ 2'/ | 3' 2' 1' 6 |
      1' 7 6 5 | 5/ 5// 6// 3/ 2/ 3- | 3/ 5/ 6 2' 7 | 1' 7/ 5/ 6- |
      1' 7 6 5 | 5/ 5// 6// 3/ 2/ 3 3/ 5/ | 6/ 6 6/ 1' 2' | 7-- 6/ 1'/ |
      2'/ 2' 3'/ 3'. 3'/ | 5'/ 6'/ 2'/ 1'/ 3' 6/ 1'/ | 2'/ 2' 3'/ 3' 3'/ 3'/ | 4'/ 3'/ 2'/ 1'/ 1' 6/ 1'/ |
      2'/ 2' 3'/ 3'. 3'/ | 5'/ 6'/ 2'/ 1'/ 3' 6/ 1'/ | 4' 3' 2' 1' | 1'/ 2'/ 7/ 5/ 6 6/ 1'/ |
      2'/ 2' 3'/ 3'. 3'/ | 5'/ 6'/ 2'/ 1'/ 3' 6/ 1'/ | 2'/ 2' 3'/ 3' 3'/ 3'/ | 4'/ 3'/ 2'/ 1'/ 1' 6/ 1'/ |
      2'/ 2' 3'/ 3'. 3'/ | 5'/ 6'/ 2'/ 1'/ 3' 6/ 1'/ | 4' 3' 2' 1' | 2'/ 1'/ 3'/ 5'/ 6'-
      2/ 3/ 6,// 5,// 6,// 5,// 2/ 3/ 6,// 5,// 6,// 5,// | 2/ 3/ 6,// 5,// 6,// 5,// 1/ 7,/ 6/ 5/ | 2/ 3/ 6,// 5,// 6,// 5,// 2/ 3/ 6,// 5,// 6,// 5,// | 2'/ 3'/ 5'/ 1''/ 7'// 1''// 7'// 6'// 5'/ 3'/ |
      2/ 3/ 6,// 5,// 6,// 5,// 2/ 3/ 6,// 5,// 6,// 5,// | 2/ 3/ 6,// 5,// 6,// 5,// 1/ 7,/ 6/ 5/ | 3'// 2'// 3'// 5'// 6'// 5'// 3'// 2'// 6/ 1'/ 3'/ 5'/ | 6'/ 6' 5'/ 6'- |
      6 6/. 5// 6/ 1'/ 2'/ 3'/ | 6 6/. 5// 6/ 5/ 3/ 5/ | 6 6/. 5// 6/ 1'/ 2'/ 3'/ | 3' 2' 1' 6 |
      6 6/. 5// 6/ 1'/ 2'/ 3'/ | 6 6/. 5// 6/ 5/ 5/ 3/ | 6 6/. 5// 5/ 6/ 1'/ 2'/ | 3' 2' 1' 6 |
      1' 7 6 5 | 5/ 5// 6// 3/ 2/ 3- | 3/ 5/ 6 2' 7 | 1' 7/ 5/ 6- |
      1' 7 6 5 | 5/ 5// 6// 3/ 2/ 3 3/ 5/ | 6/ 6 6/ 1' 2' | 7-- 6/ 1'/ |
      2'/ 2' 3'/ 3'. 3'/ | 5'/ 6'/ 2'/ 1'/ 3' 6/ 1'/ | 2'/ 2' 3'/ 3' 3'/ 3'/ | 4'/ 3'/ 2'/ 1'/ 1' 6/ 1'/ |
      2'/ 2' 3'/ 3'. 3'/ | 5'/ 6'/ 2'/ 1'/ 3' 6/ 1'/ | 4' 3' 2' 1' | 1'/ 2'/ 7/ 5/ 6 6/ 1'/ |
      2'/ 2' 3'/ 3'. 3'/ | 5'/ 6'/ 2'/ 1'/ 3' 6/ 1'/ | 2'/ 2' 3'/ 3' 3'/ 3'/ | 4'/ 3'/ 2'/ 1'/ 1' 6/ 1'/ |
      2'/ 2' 3'/ 3'. 3'/ | 5'/ 6'/ 2'/ 1'/ 3' 6/ 1'/ | 4' 3' 2' 1' | 2'/ 1'/ 3'/ 5'/ 6'-


              `;
        var tones = getSongTone(song);
        var tonesIndex = 0;
        var done = true;

        clearInterval(timer);
        timer = setInterval(() => {
          if (tonesIndex >= tones.length) {
            clearInterval(timer);
            return;
          }
          if (done) {
            done = false;
            var tone = tones[tonesIndex];
            var os = ac.createOscillator();
            var gain = ac.createGain();

            console.log(tone);

            os.connect(analyser);
            analyser.connect(gain);
            gain.connect(ac.destination);

            gain.gain.setValueAtTime(0, ac.currentTime);
            gain.gain.linearRampToValueAtTime(1, ac.currentTime + 0.01);
            gain.gain.exponentialRampToValueAtTime(
              0.001,
              ac.currentTime + 1
            );
            gain.gain.setValueAtTime(1, ac.currentTime + tone.time - 0.2);

            os.frequency.value = tone.tone;
            os.type = "sine";
            os.start(ac.currentTime);
            os.stop(ac.currentTime + tone.time);

            setTimeout(() => {
              tonesIndex++;
              done = true;
              os.disconnect();
              analyser.disconnect();
              gain.disconnect();
            }, tone.time * 1000);
          }
        }, 10);
      };
    </script>
  </body>
</html>
